/*
    Copyright 2006 Andrew Wilkinson <andrew@indiegigs.co.uk>

    This file is part of libgp2x.

    libgp2x is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    libgp2x is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with libgp2x; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifdef GP2x_940
#include "gp2x_940t.h"
#endif
#include "gp2x_internal.h"

#undef printf

/* Where the memory we control starts */
void* GP2x_upper_memory_base;

/* The size (in bytes) of the memory we control */
unsigned long GP2x_upper_memory_size;

/* Malloc lock memory */
unsigned int* GP2x_malloc_my_lock;
unsigned int* GP2x_malloc_their_lock;
#define LOCKED 0xFFFFFFFF

#define USE_MAGIC 1
#if USE_MAGIC
#define MAGIC 0xABCDE012
#define set_magic(block) block->magic = MAGIC
#define test_magic(block) (block->magic == MAGIC)
#else

#define set_magic(block)
#define test_magic(block) 1
#endif

struct BlockHeader_t {
    unsigned int in_use : 1;
    unsigned int final_block : 1;
    unsigned int size : 30;
#if USE_MAGIC
    unsigned int magic : 32;
#endif
};
typedef struct BlockHeader_t BlockHeader;

static inline BlockHeader* get_header(void* ptr) {
    return (BlockHeader*)((unsigned long)ptr - sizeof(BlockHeader));
}

static inline BlockHeader* get_block(void* ptr) {
    return (BlockHeader*)((unsigned long)ptr + sizeof(BlockHeader));
}

static inline BlockHeader* get_next(void* ptr) {
    BlockHeader* header = ptr;
    if(header->final_block == 1) {
        return NULL;
    } else {
        return (BlockHeader*)((unsigned long)ptr + header->size + sizeof(BlockHeader));
    }
}

void GP2x_upper_memory_init(unsigned long base, unsigned long size) {
    GP2x_upper_memory_base = (void*)GP2x_convert_ptr_from(base) + 2*sizeof(unsigned int);
#ifndef GP2x_940
    GP2x_malloc_my_lock = (unsigned int*)GP2x_upper_memory_base - 2*sizeof(unsigned int);
    GP2x_malloc_their_lock = (unsigned int*)GP2x_upper_memory_base - sizeof(unsigned int);
#else
    GP2x_malloc_my_lock = (unsigned int*)GP2x_upper_memory_base - sizeof(unsigned int);
    GP2x_malloc_their_lock = (unsigned int*)GP2x_upper_memory_base - 2*sizeof(unsigned int);
#endif
#ifndef GP2x_940
    *GP2x_malloc_my_lock = 0;
    *GP2x_malloc_their_lock = 0;
#endif
    GP2x_upper_memory_size = size - sizeof(BlockHeader) - sizeof(unsigned int);

    BlockHeader* header = (BlockHeader*)GP2x_upper_memory_base;
    header->in_use = 0;
    header->final_block = 1;
    header->size = GP2x_upper_memory_size;
    set_magic(header);
}

void GP2x_malloc_lock() {
    while(*GP2x_malloc_their_lock) {
#ifdef HOST_PC
        sleep(0);
#endif
    }
    *GP2x_malloc_my_lock = LOCKED;
}

void GP2x_malloc_unlock() {
    *GP2x_malloc_my_lock = 0;
}

void GP2x_upper_memory_count(unsigned int* usedmemory, unsigned int* usedblocks, unsigned int* freeblocks) {
    void* block = GP2x_upper_memory_base;

    *usedmemory = 0;
    *usedblocks = 0;
    *freeblocks = 0;

    while(block != NULL) {
        if(((BlockHeader*)block)->in_use == 1) {
            *usedmemory += ((BlockHeader*)block)->size;
            *usedblocks += 1;
        } else if(((BlockHeader*)block)->in_use == 0) {
            *freeblocks += 1;
        }
        block = get_next(block);
    }
}

#ifdef GP2x_940
void* malloc(size_t size) {
#else
void* GP2x_upper_memory_malloc(size_t size) {
#endif

    BlockHeader* block = GP2x_upper_memory_base;

    /* Just in case someone does something stupid :-) */
    if(size == 0) { return 0; }

    GP2x_malloc_lock();
    while(block != NULL && (block->in_use == 1 || block->size < size)) {
        if(!test_magic(block)) {
            GP2x_malloc_unlock();
            return NULL;
        }
        block = get_next(block);
    }
    if(block == NULL) {
        GP2x_malloc_unlock();
        return NULL;
    } else if(block->size >= size && block->size <= size + sizeof(BlockHeader)) {
        block->in_use = 1;

        GP2x_malloc_unlock();
        return get_block(block);
    } else {
        int real_size = block->size;
        int final = block->final_block;
        block->in_use = 1;
        block->final_block = 0;
        block->size = size;

        BlockHeader* new_block = get_next(block);
        new_block->size = real_size - size - sizeof(BlockHeader);
        new_block->in_use = 0;
        new_block->final_block = final;
        set_magic(new_block);

        GP2x_malloc_unlock();
        return get_block(block);
    }
}

#ifdef GP2x_940
void* realloc(void* ptr, size_t size) {
#else
void* GP2x_upper_memory_realloc(void* ptr, size_t size) {
#endif
    void* nptr;
    if(size == 0) {
#ifdef GP2x_940
        free(ptr);
#else
        GP2x_upper_memory_free(ptr);
#endif
        return NULL;
    } else if(ptr == NULL) {
#ifdef GP2x_940
        return malloc(size);
#else
        return GP2x_upper_memory_malloc(size);
#endif
    } else {
#ifdef GP2x_940
        nptr = malloc(size);
        memcpy(nptr, ptr, size);
        free(ptr);
#else
        nptr = GP2x_upper_memory_malloc(size);
        memcpy(nptr, ptr, size);
        GP2x_upper_memory_free(ptr);
#endif
        return nptr;
    }
}

#ifdef GP2x_940
void free(void* ptr) {
#else
void GP2x_upper_memory_free(void* ptr) {
#endif
    BlockHeader* block = get_header(ptr);

    GP2x_malloc_lock();
    if(!test_magic(block)) {
        GP2x_malloc_unlock();
        return;
    }

    block->in_use = 0;

    while(block->final_block == 0 && get_next(block)->in_use == 0) {
        int next_size = get_next(block)->size;
        block->final_block = get_next(block)->final_block;
        block->size = block->size + sizeof(BlockHeader) + next_size;
    }

    GP2x_malloc_unlock();
}
